package org.oimwrapper.connectors.common;

import Thor.API.Operations.*;

import java.util.*;import org.oimwrapper.connectors.*;

/**
 * Generates new base audit snapshots on the system, and clears all older 
 * audit records.
 *
 * This task expects the following parameters, in addition to the standard
 * parameters in the {@link OIMTaskBase} class.
 * <ul>
 * <li><code>Threads</code> The number of concurrent threads to run to process 
 *                          records to OIM.  By default, ten submitting threads 
 *                          are run.</li>
 * <li><code>Username</code> The username of a single user to clear the old 
 *                           audit records for.</li>
 * <li><code>Group</code> If group profile auditing is supported, the name of a 
 *                        single group to clear the old audit records for.  If 
 *                        the <code>Username</code> parameter is present this 
 *                        will be ignored.</li>
 * </ul>
 */
public class OIMClearAuditData extends OIMTaskBase {

	/**
	 * True if a stop has been requested for this task.
	 */
	private boolean stop;
	
	/**
	 * The audit operations interface used by this task.
	 */
	protected tcAuditOperationsIntf auditOp;

	/**
	 * Indicates if the system is using OIM 9.1 or not.
	 */
	protected boolean is91;
	
	/**
	 * A set used to keep track of entries are being processed.
	 */
	private Set audJmsKeys;
	
	/**
	 * Default constructor.
	 */
	public OIMClearAuditData() {
		super("OIMWRAPPER.TASK");
		stop = false;
		audJmsKeys = new HashSet();
	}

	/**
	 * This method is called by the scheduler to execute the task instance.
	 */
	public void execute() {
		logger.debug("Entering clearAuditData.execute()");
		try {
			is91 = checkIfOimVersion("9.1");
			auditOp = (tcAuditOperationsIntf) getUtility(tcAuditOperationsIntf.class);
			int maxWorkers = Integer.parseInt(getDefaultAttribute("Threads", "10"));
			String username = getAttribute("Username");
			String group = getAttribute("Group");
			logger.info("Truncating the audit queue...");
			truncateTables(new String[] {"aud_jms"});
			if (isEmpty(username)) {
				long[] userKeys = getOimUserKeys("*");
				for (int i = 0; !stop && i < userKeys.length; i++) {
					if (i == 0 || (i + 1) % 100 == 0) {
						logger.info("Processing user " + Integer.toString(i + 1) + " of " + Integer.toString(userKeys.length));
					} else {
						logger.debug("Processing user " + Integer.toString(i + 1) + " of " + Integer.toString(userKeys.length));
					}
					startThread(maxWorkers, new userWorker(this, userKeys[i]));
				}
			} else {
				processSingleUser(getOimUser(username));
			}
			if (is91 && isEmpty(username)) {
				if (isEmpty(group)) {
					long[] groupKeys = findOimGroups("Groups.Group Name", "*");
					for (int i = 0; !stop && i < groupKeys.length; i++) {
						if (i % 100 == 0 || i + 1 == groupKeys.length) {
							logger.info("Processing group " + Integer.toString(i + 1) + " of " + Integer.toString(groupKeys.length));
						}
						startThread(maxWorkers, new groupWorker(this, groupKeys[i]));
					}
				} else {
					processSingleGroup(getOimGroup(group));
				}
			}
			waitForAllThreads();
			logger.info("Finished clearing all audit information");
		} catch (Exception e) {
			OIMExceptionHandler.handleException(logger, e, getClass());
		}
		logger.debug("Exiting clearAuditData.execute()");
	}

	/**
	 * This method is called by the scheduler to stop a running task instance.
	 * 
	 * @return <code>true</code>
	 */
	public boolean stop() {
		logger.debug("Entering clearAuditData.stop()");
		logger.warn(" ---- Stopping current task ---- ");
		stop = true;
		logger.debug("Exiting clearAuditData.stop()");
		return stop;
	}
	
	/**
	 * Clear tables by attempting to truncate them first, then deleting rows directly.
	 * 
	 * @param Tables The list of tables to clear, in order.
	 * @exception Exception
	 */
	protected void truncateTables(String[] Tables) throws Exception {
		logger.debug("Entering clearAuditData.truncateTables()");
		int size = 100000;
		for (int i = 0; !stop && i < Tables.length; i++) {
			logger.debug("Clearing " + Tables[i] + "...");
			for (int j = 1; !stop; j += size) {
				logger.debug("Clearing rows " + Integer.toString(j) + " to " + Integer.toString(j + size - 1) + " from " + Tables[i]);
				String count = (String) getOimData("select count(*) as count from " + Tables[i])[0].get("COUNT");
				if (count.equals("0")) {
					break;
				}
				executeOimQuery("delete from " + Tables[i] + " where rownum < " + Integer.toString(size));
			}
		}
		logger.debug("Exiting clearAuditData.truncateTables()");
	}
	
	/**
	 * Controls the processing of a single aud_jms entry so that record is only
	 * processed once.
	 * 
	 * @param AudJmsKey The aud_jms key of the entry to process.
	 * @return <code>true</code> if the record should be processed, or <code>false</code>
	 *         if it is already being processed.
	 */
	protected synchronized boolean lockAudJmsKey(int AudJmsKey) {
		logger.debug("Entering clearAuditData.lockAudJmsKey()");
		boolean result = false;
		Integer key = new Integer(AudJmsKey);
		if (!audJmsKeys.contains(key)) {
			audJmsKeys.add(key);
			result = true;
		}
		logger.debug("Exiting clearAuditData.lockAudJmsKey()");
		return result;
	}
	
	/**
	 * Processes an audit snapshot cleanup for a single user.
	 * 
	 * @param UserKey The key of the user to process.
	 * @exception Exception
	 */
	protected void processSingleUser(long UserKey) throws Exception {
		logger.debug("Entering clearAuditData.processSingleUser()");
		String usrCleanupFilter = "";
		Map[] usrRecords = getOimData("select upa_usr_key from upa_usr where usr_key = " + Long.toString(UserKey));
		for (int i = 0; i < usrRecords.length; i++) {
			if (i == 0) {
				usrCleanupFilter += "where upa_usr_key in (";
			} else {
				usrCleanupFilter += ",";
			}
			usrCleanupFilter += usrRecords[i].get("UPA_USR_KEY");
		}
		if (usrCleanupFilter.length() > 0) {
			usrCleanupFilter += ")";
		}
		String upaCleanupFilter = "";
		Map[] upaRecords = getOimData("select upa_key from upa where usr_key = " + Long.toString(UserKey) + " and upa_key not in (select upa_key from atd inner join apt on atd.apt_key = apt.apt_key inner join atr on apt.atr_key = atr.atr_key where atr_completion_time is not null)");
		for (int i = 0; i < upaRecords.length; i++) {
			if (i == 0) {
				upaCleanupFilter += "where upa_key in (";
			} else {
				upaCleanupFilter += ",";
			}
			upaCleanupFilter += upaRecords[i].get("UPA_KEY");
		}
		if (upaCleanupFilter.length() > 0) {
			upaCleanupFilter += ")";
		}
		auditOp.generateSnapshot(UserKey);
		Map[] jmsRecords = getOimData("select aud_jms_key from aud_jms");
		for (int i = 0; i < jmsRecords.length; i++) {
			int audJmsKey = Integer.parseInt((String) jmsRecords[i].get("AUD_JMS_KEY"));
			if (lockAudJmsKey(audJmsKey)) {
				logger.debug("Processing: {audJmsKey=" + Integer.toString(audJmsKey) + "}");
				auditOp.processAuditMessageOnline(audJmsKey);
			}
		}
		if (usrCleanupFilter.length() > 0) {
			if (is91) {
				String query = "delete from upa_ud_formfields where upa_ud_forms_key in (";
				query += "select upa_ud_forms_key from upa_ud_forms where upa_resource_key in (";
				query += "select upa_resource_key from upa_resource " + usrCleanupFilter + "))";
				logger.debug("query = " + query);
				executeOimQuery(query, false);
				query = "delete from upa_ud_forms where upa_resource_key in (";
				query += "select upa_resource_key from upa_resource " + usrCleanupFilter + ")";
				logger.debug("query = " + query);
				executeOimQuery(query, false);
			}
			String query = "delete from upa_resource " + usrCleanupFilter;
			logger.debug("query = " + query);
			executeOimQuery(query, false);
			query = "delete from upa_fields " + usrCleanupFilter;
			logger.debug("query = " + query);
			executeOimQuery(query, false);
			query = "delete from upa_grp_membership " + usrCleanupFilter;
			logger.debug("query = " + query);
			executeOimQuery(query, false);
			query = "delete from upa_usr " + usrCleanupFilter;
			logger.debug("query = " + query);
			executeOimQuery(query);
		}
		if (upaCleanupFilter.length() > 0) {
			String query = "delete from upa " + upaCleanupFilter;
			logger.debug("query = " + query);
			executeOimQuery(query);
		}
		logger.debug("Exiting clearAuditData.processSingleUser()");
	}
	
	/**
	 * Processes an audit snapshot cleanup for a single group.
	 * 
	 * @param GroupKey The key of the group to process.
	 * @exception Exception
	 */
	protected void processSingleGroup(long GroupKey) throws Exception {
		logger.debug("Entering clearAuditData.processSingleGroup()");
		String gpaCleanupFilter = "";
		Map[] gpaRecords = getOimData("select gpa_key from gpa where ugp_key = " + Long.toString(GroupKey));
		for (int i = 0; i < gpaRecords.length; i++) {
			if (i == 0) {
				gpaCleanupFilter += "where gpa_key in (";
			} else {
				gpaCleanupFilter += ",";
			}
			gpaCleanupFilter += gpaRecords[i].get("GPA_KEY");
		}
		if (gpaCleanupFilter.length() > 0) {
			gpaCleanupFilter += ")";
		}
		auditOp.generateGPASnapshot(GroupKey);
		Map[] jmsRecords = getOimData("select aud_jms_key from aud_jms");
		for (int i = 0; i < jmsRecords.length; i++) {
			int audJmsKey = Integer.parseInt((String) jmsRecords[i].get("AUD_JMS_KEY"));
			if (lockAudJmsKey(audJmsKey)) {
				logger.debug("Processing: {audJmsKey=" + Integer.toString(audJmsKey) + "}");
				auditOp.processAuditMessageOnline(audJmsKey);
			}
		}
		if (gpaCleanupFilter.length() > 0) {
			String query = "delete from gpa " + gpaCleanupFilter;
			logger.debug("query = " + query);
			executeOimQuery(query);
		}
		logger.debug("Exiting clearAuditData.processSingleGroup()");
	}
	
	/**
	 * Worker Thread for a User.
	 */
	private class userWorker extends OIMTaskThreadBase {
		
		/**
		 * The invoking task.
		 */
		private OIMClearAuditData task;
		
		/**
		 * The user to process.
		 */
		private long userKey;
		
		/**
		 * Default Constructor.
		 * 
		 * @param Task The invoking task.
		 * @param UserKey The user key to process.
		 */
		private userWorker(OIMClearAuditData Task, long UserKey) {
			super(Task);
			task = Task;
			userKey = UserKey;
		}
		
		/**
		 * Processes a single task.
		 */
		public void runOimThread() throws Exception {
			task.processSingleUser(userKey);
		}
	}
	
	/**
	 * Worker Thread for a Group.
	 */
	private class groupWorker extends OIMTaskThreadBase {
		
		/**
		 * The invoking task.
		 */
		private OIMClearAuditData task;
		
		/**
		 * The user to process.
		 */
		private long groupKey;
		
		/**
		 * Default Constructor.
		 * 
		 * @param Task The invoking task.
		 * @param GroupKey The group key to process.
		 */
		private groupWorker(OIMClearAuditData Task, long GroupKey) {
			super(Task);
			task = Task;
			groupKey = GroupKey;
		}
		
		/**
		 * Processes a single task.
		 */
		public void runOimThread() throws Exception {
			task.processSingleGroup(groupKey);
		}
	}
}